use crate::*;

impl<T: PartialEq> Palette<T> {
    pub fn new(extent: Extent) -> Self {
        Self {
            entries: vec![],
            indices: vec![None; extent.get_indices_count()],
            extent,
        }
    }

    fn find_entry_mut(&mut self, t: &T) -> Option<(usize, &mut RcEntry<T>)> {
        self.entries
            .iter_mut()
            .enumerate()
            .find(|(_, val)| val.eq(t))
    }

    pub fn insert(&mut self, loc: impl Location, t: T) {
        let index = if let Some((idx, entry)) = self.find_entry_mut(&t) {
            entry.add_ref();
            idx
        } else {
            let mut entry = RcEntry::new(t);
            entry.add_ref();
            self.entries.push(entry);
            self.entries.len() - 1
        };
        if let Some(location) = loc.locate(&self.extent) {
            self.indices[location].replace(index);
        }
    }

    pub fn remove(&mut self, loc: impl Location) {
        if let Some(location) = loc.locate(&self.extent) {
            if let Some(idx) = self.indices[location] {
                self.entries[idx].release();
                if self.entries[idx].is_recyclable() {
                    self.entries.swap_remove(idx);
                    let swaped_idx = self.entries.len();
                    if swaped_idx != idx {
                        self.indices
                            .iter_mut()
                            .filter(|index| index.is_some_and(|i| i == swaped_idx))
                            .for_each(|index| {
                                index.replace(idx);
                            });
                    }
                }
            }
            self.indices[location] = None;
        }
    }

    pub fn get(&self, loc: impl Location) -> Option<&T> {
        loc.locate(&self.extent)
            .map(|loc| self.indices[loc].map(|i| &self.entries[i].t))
            .unwrap_or_default()
    }

    pub fn get_mut(&mut self, loc: impl Location) -> Option<&mut T> {
        loc.locate(&self.extent)
            .map(|loc| self.indices[loc].map(|i| &mut self.entries[i].t))
            .unwrap_or_default()
    }

    pub fn extent(&self) -> Extent {
        self.extent
    }
}

impl<T: PartialEq, L: Location> ops::Index<L> for Palette<T> {
    type Output = T;

    fn index(&self, index: L) -> &Self::Output {
        self.get(index).unwrap()
    }
}

impl<T: PartialEq, L: Location> ops::IndexMut<L> for Palette<T> {
    fn index_mut(&mut self, index: L) -> &mut Self::Output {
        self.get_mut(index).unwrap()
    }
}